package com.namazustudios.catfish.support;

import java.lang.reflect.Field;

import com.google.inject.Injector;
import com.google.inject.MembersInjector;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import com.namazustudios.catfish.CatfishException;
import com.namazustudios.catfish.annotation.Entity;
import com.namazustudios.catfish.annotation.RequiresInjection;
import com.namazustudios.catfish.utility.FieldIterable;

/**
 * Provides support for the {@link RequiresInjection} annotation.
 * 
 * @author patricktwohig
 *
 */
public final class RequiresInjectionSupport implements TypeListener {

	@Override
	public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 
		final Provider<Injector> provider = encounter.getProvider(Injector.class);

		encounter.register(new MembersInjector<Object>() {

			@Override
			public void injectMembers(Object object) {
				Injector injector = provider.get();
				RequiresInjectionSupport.this.injectMembers(injector, object);
			}

		});

	}

	private void injectMembers(Injector injector, Object object) {
        Class<?> cls = object.getClass();

        for (Field field : new FieldIterable(cls)) {

            boolean accessable = field.isAccessible();

            field.setAccessible(true);

            try {
                if (field.getType().isAnnotationPresent(RequiresInjection.class)) {
                	Object val;

                	if ((val = field.get(object)) != null) {
                        injector.injectMembers(val);
                    }

                } else if (field.getType().isAnnotationPresent(Entity.class)) {
                	Object val;

                	if ((val = field.get(object)) != null) {
                        injector.injectMembers(val);
                    }

                } else if (field.getType().isAssignableFrom(Iterable.class)) {
                    Iterable<?> iterable = (Iterable<?>) field.get(object);

                    if (iterable != null) {
                        for (Object obj : iterable) {
                        	if (obj != null && obj.getClass().isAnnotationPresent(Entity.class)) {
                                injector.injectMembers(obj);
                            }
                        }
                    }

                }
            } catch (IllegalAccessException ex) {
                throw new CatfishException("Could not inject fields.", ex);
            } finally {
                field.setAccessible(accessable);
            }
        }

	}

}
